package ast;

import java.util.ArrayList;

public class CheckVisitor implements Visitor {

	SymbolTable rootTable;
	SymbolTable currentTable;

	public CheckVisitor(SymbolTable table) {
		rootTable = currentTable = table;
	}

	public Boolean visit(List n) {
		return CheckBoolean(n);
	}

	public Boolean visit(AddNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(MinusNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(DivideNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(TimesNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(IntNumNode n) {
		return true;
	}

	public Boolean visit(RealNumNode n) {
		return true;
	}

	public Boolean visit(CharListNode n) {
		return true;
	}

	public Boolean visit(BooleanNode n) {
		return true;
	}

	public Boolean visit(LeNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(LeqNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(GeNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(GeqNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(ExpNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(OrNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(AndNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(EqNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(NeqNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(InNode n) {
		return CheckBoolean(n);

	}

	public Boolean visit(AssignNode n) {
		return CheckBoolean(n);
	}

	public Boolean visit(IfNode n) {
		Boolean bool = true;
		Boolean bool1 = true;
		Boolean bool2 = true;
		if (n.e != null)
			bool = (Boolean) n.e.accept(this);
		if (n.left != null)
			bool1 = (Boolean) n.left.accept(this);
		if (n.right != null)
			bool2 = (Boolean) n.right.accept(this);
		return bool && bool1 && bool2;
	}

	public Boolean visit(ReturnStmtNode n) {
		return CheckLeftBoolean(n);

	}

	public Boolean visit(VarNode n) {
		return dotCallScopeCheck(n);
	}

	public Boolean visit(ParamNode n) {
		// TODO CHECK!!!
		return true;
	}

	public Boolean visit(BodyNode n) {
		Boolean varCheck = new Boolean(false);
		Boolean leftCheck = new Boolean(false);
		currentTable = currentTable.enterScope(Integer.toString(currentTable
				.getCheckBodyCount()));
		if (n.vardecs != null) {
			varCheck = (Boolean) n.vardecs.accept(this);
		}
		leftCheck = (Boolean) n.left.accept(this);
		currentTable = currentTable.exitScope();
		if (varCheck.booleanValue() && leftCheck.booleanValue()) {
			return varCheck;
		} else {
			return false;
		}
	}

	public Boolean visit(CallVarExprNode n) {
		return CheckLeftBoolean(n);
	}

	public Boolean visit(CallVarNode n) {
		return dotCallScopeCheck(n);
	}

	public Boolean visit(CallVarStmtNode n) {
		return CheckLeftBoolean(n);
	}

	public Boolean visit(ClassDecNode n) {
		currentTable = currentTable.enterScope(n.id);
		n.left.accept(this);
		currentTable = currentTable.exitScope();
		return null;
	}

	public Boolean visit(ClassDefNode n) {
		return CheckLeftBoolean(n);
	}

	public Boolean visit(InstVarDecNode n) {
		return CheckLeftBoolean(n);
	}

	public Boolean visit(LenNode n) {
		return CheckLeftBoolean(n);
	}

	public Boolean visit(MainProgramNode n) {
		Boolean bool = true;
		Boolean bool2 = true;
		if (n.declist != null)
			bool = (Boolean) n.declist.accept(this);
		if (n.left != null)
			bool2 = (Boolean) n.left.accept(this);
		return bool && bool2;
	}

	public Boolean visit(MethodBodyNode n) {
		Boolean bool = true;
		Boolean bool1 = true;
		Boolean bool2 = true;
		if (n.vardeclist != null)
			bool = (Boolean) n.vardeclist.accept(this);
		if (n.left != null)
			bool1 = (Boolean) n.left.accept(this);
		if (n.right != null)
			bool2 = (Boolean) n.right.accept(this);
		return bool && bool1 && bool2;
	}

	public Boolean visit(MethodDecNode n) {
		String scopeName;
		if (n.params != null) {
			String paramStr = "";
			for (TypeDecStoreSymbol btd : paramListToArray(n.params)) {
				currentTable.put(btd.id, btd);
				paramStr += ":" + btd.type;
			}
			scopeName = n.id + "." + n.access + "." + n.type + paramStr;
		} else {
			scopeName = n.id + "." + n.access + "." + n.type;
		}
		currentTable = currentTable.enterScope(scopeName);
		n.left.accept(this);
		currentTable.exitScope();
		return null;
	}

	public Boolean visit(NotinNode n) {
		return CheckBoolean(n);
	}

	public Boolean visit(NotNode n) {
		return CheckLeftBoolean(n);
	}

	public Boolean visit(ObjTypeDecNode n) {
		Boolean typeLookup = classLookup(n.type);
		Boolean varLookup = varLookup(n.id, n.type, currentTable);
		return typeLookup && varLookup;
	}

	public Object visit(BasicTypeDecNode n) {
		Boolean varLookup = varLookup(n.id, n.type, currentTable);
		Boolean left = (Boolean) n.left.accept(this);
		return varLookup && left;
	}

	public Boolean visit(RepeatNode n) {
		return CheckBoolean(n);
	}

	public Boolean visit(VarExprNode n) {
		return CheckLeftBoolean(n);
	}

	public Boolean visit(FormalparamNode n) {
		return CheckBoolean(n);
	}

	public Boolean visit(ParamlistNode n) {
		return CheckBoolean(n);
	}

	public Boolean visit(ParanExprNode n) {
		Boolean bool = true;
		if (n.en != null)
			bool = (Boolean) n.en.accept(this);
		return bool;
	}

	public Object visit(RootNode n) {
		return CheckBoolean(n);
	}

	private Boolean CheckBoolean(Node n) {
		Boolean bool = true;
		Boolean bool2 = true;
		if (n.left != null)
			bool = (Boolean) n.left.accept(this);
		if (n.right != null)
			bool2 = (Boolean) n.right.accept(this);
		return bool && bool2;
	}

	private Boolean CheckLeftBoolean(Node n) {
		Boolean bool = false;
		if (n.left != null)
			bool = (Boolean) n.left.accept(this);
		return bool;
	}

	private ArrayList<TypeDecStoreSymbol> paramListToArray(List list) {
		ArrayList<TypeDecStoreSymbol> tmp = new ArrayList<TypeDecStoreSymbol>();
		List tmpList = list;
		ParamNode tdn;
		while (tmpList != null && tmpList.left != null) {
			tdn = (ParamNode) tmpList.left;
			tmp.add(new TypeDecStoreSymbol(tdn.id, null, tdn.type));
			if (tmpList.right == null) {
				break;
			} else {
				tmpList = (List) tmpList.right;
			}
		}
		return tmp;
	}

	private Boolean varLookup(String elementKey, String type, SymbolTable scope) {
		do {
			if (varLookupInScope(elementKey, type, scope)) {
				return true;
			}
			scope = scope.parent;
		} while (scope != rootTable);
		return false;
	}

	private Boolean varLookupInScope(String elementKey, String type, SymbolTable scope) {
		if (scope.table.containsKey(elementKey)) {
			for (StoreSymbol symbol : scope.table.get(elementKey)) {
				if (symbol.getType().equals(type)) {
					return true;
				}
			}
		}
		return false;
	}
	
	private Boolean paramsMatch(ArrayList<TypeDecStoreSymbol> params1, ArrayList<TypeDecStoreSymbol> params2) {
		if (params1.size() != 0) {
			if (params1.size() == params2.size()) {
				Boolean match = true;
				for (TypeDecStoreSymbol paramPassed : params1) {
					for (TypeDecStoreSymbol paramChecked : params2) {
						if (!paramPassed.type
								.equals(paramChecked.type)) {
							match = false;
						}
					}
				}
				return match;
			}
			else {
				return false;
			}
		} else {
			return true;
		}
	}

	//TODO fix. 
	// Finds public methods lookup globally.
	private Boolean methodGlobalLookup(String id, ArrayList<TypeDecStoreSymbol> params) {
		for (String key : rootTable.table.keySet()) {
			for (StoreSymbol classSymbol : rootTable.get(key)) {
				if (classSymbol.getSymbolType().equals("class")) {
					ClassStoreSymbol classTemp = (ClassStoreSymbol) classSymbol;
					for (String name : classTemp.publicSymbols.table.keySet()) { //error here.
						for (StoreSymbol method : classTemp.publicSymbols.get(name)) {
							if (method.getSymbolType().equals("method")) {
								MethodStoreSymbol methodTemp = (MethodStoreSymbol) method;
								ArrayList<TypeDecStoreSymbol> paramsTemp = methodTemp.params;
								if (name.equals(id)
										&& paramsMatch(params, paramsTemp)) {
									return true;
								}
							}
						}
					}
				}
			}
		}
		return false;
	}
	
	private Boolean methodLocalLookup(String id, ArrayList<TypeDecStoreSymbol> params, SymbolTable scope) {
			for (StoreSymbol method : scope.get(id)) {
				if (method.getSymbolType().equals("method")) {
					MethodStoreSymbol methodTemp = (MethodStoreSymbol) method;
					ArrayList<TypeDecStoreSymbol> paramsTemp = methodTemp.params;
					if (paramsMatch(params, paramsTemp)) {
						return true;
					}
				}
			}
		return false;
		//TODO implement. search for a method in one specific scope. possibly need another method/parameter for public/private searches.
	}
	
	//TODO fix. should look through all the methods in the program. (?) 
	private MethodStoreSymbol methodSymbolGet(String id,
			ArrayList<TypeDecStoreSymbol> params) {
		for (String key : rootTable.table.keySet()) {
			for (StoreSymbol classSymbol : rootTable.get(key)) {
				if (classSymbol.getSymbolType().equals("class")) {
					ClassStoreSymbol classTemp = (ClassStoreSymbol) classSymbol;
					for (String name : classTemp.publicSymbols.table.keySet()) { //error here.
						for (StoreSymbol method : classTemp.publicSymbols.get(name)) {
							if (method.getSymbolType().equals("method")) {
								MethodStoreSymbol methodTemp = (MethodStoreSymbol) method;
								ArrayList<TypeDecStoreSymbol> paramsTemp = methodTemp.params;
								if (name.equals(id)
										&& paramsMatch(params, paramsTemp)) {
									return methodTemp;
								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	private Boolean isBasic(String type) {
		if (type.equals("bool") || type.equals("int") || type.equals("double")
				|| type.equals("chrlist")) {
			return true;
		} else {
			return false;
		}
	}

	private Boolean classLookup(String className) {
		return rootTable.table.containsKey(className);
	}

	private Boolean typeLookup(String type) {
		if (classLookup(type) || isBasic(type)) {
			return true;
		}
		else {
			return false;
		}
	}
	
	private ArrayList<StoreSymbol> dotCallToArray(Node n) {
		ArrayList<StoreSymbol> dotCall = new ArrayList<StoreSymbol>();
		Node tmpNode = n;
		while (tmpNode != null && tmpNode.left != null) {
			if (tmpNode.right == null) {
				VarNode tmp2 = (VarNode) tmpNode;
				dotCall.add(new TypeDecStoreSymbol(tmp2.id, null, null));
				tmpNode = tmp2.left;
			} else {
				CallVarNode tmp2 = (CallVarNode) tmpNode;
				ArrayList<TypeDecStoreSymbol> params = paramListToArray((List) tmp2.right);
				dotCall.add(new MethodStoreSymbol(null, methodSymbolGet(
						tmp2.id, params).rType, tmp2.id, params));
				tmpNode = tmpNode.left;
			}
		}
		return dotCall;
	}

	private Boolean dotCallScopeCheck(Node n) {
		ArrayList<StoreSymbol> dotCall = dotCallToArray(n);
		Boolean scopeCheck = true;
		if (dotCall.size() > 1) {
			String lastType = null;
			SymbolTable currentScope = currentTable;
			int i;
			for (i = dotCall.size() - 1; i == 0; i--) {
				if (dotCall.get(i).getSymbolType().equals("method")) {
					MethodStoreSymbol method = (MethodStoreSymbol) dotCall.get(i);
					if (methodLocalLookup(method.id, method.params, currentScope)) {
						lastType = method.getType();
					}
					else {
						scopeCheck = false;
					}
				}
				else {
					TypeDecStoreSymbol typeDec = (TypeDecStoreSymbol) dotCall.get(i);
					if (isBasic(typeDec.getType())) {
						if(!varLookup(typeDec.id, typeDec.type, currentScope)) {
							scopeCheck = false;
						}
					}
					else {
						if (varLookup(typeDec.id, typeDec.type, currentScope)) {
							lastType = typeDec.type;
						}
						else {
							scopeCheck = false;
						}
						
					}
				}
				if (!lastType.equals(currentTable.getName())) {
					for (SymbolTable classSym : rootTable.children) {
						if(classSym.getName().equals(lastType)){
							currentScope = classSym;
						}
					}
				}
				else {
					currentScope = currentTable;
				}
			}
		} else {
			if (!singleScopeCheck(dotCall.get(0), currentTable)) {
				scopeCheck = false;
			}
		}
		return scopeCheck;
	}

	private Boolean singleScopeCheck(StoreSymbol symbol, SymbolTable scope) {
		if (symbol.getSymbolType().equals("dec")) {
			TypeDecStoreSymbol tmp = (TypeDecStoreSymbol) symbol;
			return varLookup(tmp.id, tmp.type, scope);
		} else {
			if (symbol.getSymbolType().equals("method")) {
				MethodStoreSymbol symbolTemp = (MethodStoreSymbol) symbol;
				return methodLocalLookup(symbolTemp.id, symbolTemp.params, currentTable);
			} else {
				return false;
			}
		}
	}

	public Boolean visit(TypeDecNode n) {
		Boolean typeLookup = typeLookup(n.type);
		Boolean varLookup = varLookup(n.id, n.type, currentTable);
		Boolean exprCheck = (Boolean) n.left.accept(this);
		return (typeLookup && varLookup && exprCheck);
	}

	public Object visit(ExprNode n) {
		// TODO Auto-generated method stub
		return null;
	}

}